-- BBE.physics - модульная физическая система для BlazeBolt game engine
local input = require("BBE.input")

local Physics = {}
Physics.__index = Physics

-- Конфигурация по умолчанию
Physics.defaultConfig = {
    gravity = {0, -9.81, 0},
    worldBounds = {
        min = {-1000, -1000, -1000},
        max = {1000, 1000, 1000}
    },
    collisionLayers = 0xFFFF,
    fixedTimestep = 1/60,
    velocityIterations = 8,
    positionIterations = 3
}

-- Система слоев коллизий
Physics.collisionMatrix = {}
Physics.layers = {
    DEFAULT = 1,
    PLAYER = 2,
    ENEMY = 4,
    PROJECTILE = 8,
    TERRAIN = 16,
    TRIGGER = 32,
    SENSOR = 64
}

-- Матрица коллизий (какие слои сталкиваются с какими)
for i = 1, 64 do
    Physics.collisionMatrix[i] = 0xFFFF -- по умолчанию все сталкиваются со всеми
end

-- Вспомогательные математические функции
local MathUtils = {}

function MathUtils.vector3Add(a, b)
    return {a[1] + b[1], a[2] + b[2], a[3] + b[3]}
end

function MathUtils.vector3Sub(a, b)
    return {a[1] - b[1], a[2] - b[2], a[3] - b[3]}
end

function MathUtils.vector3Mul(v, s)
    return {v[1] * s, v[2] * s, v[3] * s}
end

function MathUtils.vector3Dot(a, b)
    return a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
end

function MathUtils.vector3Cross(a, b)
    return {
        a[2] * b[3] - a[3] * b[2],
        a[3] * b[1] - a[1] * b[3],
        a[1] * b[2] - a[2] * b[1]
    }
end

function MathUtils.vector3Length(v)
    return math.sqrt(v[1] * v[1] + v[2] * v[2] + v[3] * v[3])
end

function MathUtils.vector3Normalize(v)
    local len = MathUtils.vector3Length(v)
    if len > 0 then
        return {v[1] / len, v[2] / len, v[3] / len}
    end
    return {0, 0, 0}
end

function MathUtils.vector2Dot(a, b)
    return a[1] * b[1] + a[2] * b[2]
end

function MathUtils.vector2Length(v)
    return math.sqrt(v[1] * v[1] + v[2] * v[2])
end

function MathUtils.vector2Normalize(v)
    local len = MathUtils.vector2Length(v)
    if len > 0 then
        return {v[1] / len, v[2] / len}
    end
    return {0, 0}
end

-- Структура для информации о коллизии
Physics.CollisionInfo = {}
Physics.CollisionInfo.__index = Physics.CollisionInfo

function Physics.CollisionInfo.new()
    local info = setmetatable({}, Physics.CollisionInfo)
    info.hasCollision = false
    info.normal = {0, 0, 0}
    info.penetration = 0
    info.point = {0, 0, 0}
    info.bodyA = nil
    info.bodyB = nil
    info.colliderA = nil
    info.colliderB = nil
    return info
end

-- Базовый класс для физических объектов
Physics.Body = {}
Physics.Body.__index = Physics.Body

function Physics.Body.new(config)
    local body = setmetatable({}, Physics.Body)
    
    body.type = config.type or "dynamic"
    body.position = config.position or {0, 0, 0}
    body.velocity = config.velocity or {0, 0, 0}
    body.acceleration = config.acceleration or {0, 0, 0}
    body.rotation = config.rotation or {0, 0, 0}
    body.angularVelocity = config.angularVelocity or {0, 0, 0}
    body.mass = config.mass or 1.0
    body.invMass = body.mass > 0 and 1.0 / body.mass or 0
    body.restitution = config.restitution or 0.3
    body.friction = config.friction or 0.5
    body.damping = config.damping or 0.99
    body.angularDamping = config.angularDamping or 0.95
    body.collisionLayer = config.collisionLayer or Physics.layers.DEFAULT
    body.collisionMask = config.collisionMask or 0xFFFF
    body.isTrigger = config.isTrigger or false
    body.isSensor = config.isSensor or false
    body.enabled = config.enabled ~= false
    body.userData = config.userData or {}
    body.onCollision = config.onCollision
    body.onTrigger = config.onTrigger
    
    -- Коллайдеры и AABB
    body.colliders = config.colliders or {}
    body.aabb = {min = {0, 0, 0}, max = {0, 0, 0}}
    body.broadphaseId = nil
    
    -- Обновление AABB
    body:updateAABB()
    
    return body
end

function Physics.Body:updateAABB()
    if #self.colliders == 0 then
        self.aabb.min = MathUtils.vector3Add(self.position, {-0.5, -0.5, -0.5})
        self.aabb.max = MathUtils.vector3Add(self.position, {0.5, 0.5, 0.5})
        return
    end
    
    local min = {math.huge, math.huge, math.huge}
    local max = {-math.huge, -math.huge, -math.huge}
    
    for _, collider in ipairs(self.colliders) do
        if collider.enabled then
            local colliderMin, colliderMax = collider:getAABB(self.position)
            for i = 1, 3 do
                min[i] = math.min(min[i], colliderMin[i])
                max[i] = math.max(max[i], colliderMax[i])
            end
        end
    end
    
    self.aabb.min = min
    self.aabb.max = max
end

function Physics.Body:applyForce(force, point)
    point = point or self.position
    self.acceleration[1] = self.acceleration[1] + force[1] * self.invMass
    self.acceleration[2] = self.acceleration[2] + force[2] * self.invMass
    self.acceleration[3] = self.acceleration[3] + force[3] * self.invMass
end

function Physics.Body:applyImpulse(impulse, point)
    point = point or self.position
    self.velocity[1] = self.velocity[1] + impulse[1] * self.invMass
    self.velocity[2] = self.velocity[2] + impulse[2] * self.invMass
    self.velocity[3] = self.velocity[3] + impulse[3] * self.invMass
end

function Physics.Body:update(dt)
    if not self.enabled then return end
    
    -- Интеграция скорости
    self.velocity[1] = self.velocity[1] + self.acceleration[1] * dt
    self.velocity[2] = self.velocity[2] + self.acceleration[2] * dt
    self.velocity[3] = self.velocity[3] + self.acceleration[3] * dt
    
    -- Демпфирование
    self.velocity[1] = self.velocity[1] * self.damping
    self.velocity[2] = self.velocity[2] * self.damping
    self.velocity[3] = self.velocity[3] * self.damping
    
    -- Интеграция позиции
    self.position[1] = self.position[1] + self.velocity[1] * dt
    self.position[2] = self.position[2] + self.velocity[2] * dt
    self.position[3] = self.position[3] + self.velocity[3] * dt
    
    -- Сброс ускорения
    self.acceleration = {0, 0, 0}
    
    -- Обновление AABB
    self:updateAABB()
end

-- Система коллайдеров
Physics.Collider = {}
Physics.Collider.__index = Physics.Collider

function Physics.Collider.new(type, params)
    local collider = setmetatable({}, Physics.Collider)
    collider.type = type
    collider.offset = params.offset or {0, 0, 0}
    collider.enabled = params.enabled ~= false
    
    if type == "box" then
        collider.size = params.size or {1, 1, 1}
    elseif type == "sphere" then
        collider.radius = params.radius or 1
    elseif type == "capsule" then
        collider.radius = params.radius or 0.5
        collider.height = params.height or 2
    elseif type == "plane" then
        collider.normal = params.normal or {0, 1, 0}
        collider.distance = params.distance or 0
    end
    
    return collider
end

function Physics.Collider:getAABB(bodyPosition)
    local worldPos = MathUtils.vector3Add(bodyPosition, self.offset)
    
    if self.type == "box" then
        local halfSize = MathUtils.vector3Mul(self.size, 0.5)
        return {
            MathUtils.vector3Sub(worldPos, halfSize),
            MathUtils.vector3Add(worldPos, halfSize)
        }
    elseif self.type == "sphere" then
        return {
            MathUtils.vector3Sub(worldPos, {self.radius, self.radius, self.radius}),
            MathUtils.vector3Add(worldPos, {self.radius, self.radius, self.radius})
        }
    elseif self.type == "capsule" then
        local radiusVec = {self.radius, self.radius, self.radius}
        return {
            MathUtils.vector3Sub(worldPos, {self.radius, self.height * 0.5, self.radius}),
            MathUtils.vector3Add(worldPos, {self.radius, self.height * 0.5, self.radius})
        }
    elseif self.type == "plane" then
        -- Бесконечная плоскость - возвращаем очень большой AABB
        return {
            {-math.huge, -math.huge, -math.huge},
            {math.huge, math.huge, math.huge}
        }
    end
    
    return {worldPos, worldPos}
end

-- ДЕТЕКТИРОВАНИЕ КОЛЛИЗИЙ
local CollisionDetection = {}

-- Box vs Box коллизия (3D AABB)
function CollisionDetection.boxVsBox(bodyA, colliderA, bodyB, colliderB)
    local posA = MathUtils.vector3Add(bodyA.position, colliderA.offset)
    local posB = MathUtils.vector3Add(bodyB.position, colliderB.offset)
    
    local halfSizeA = MathUtils.vector3Mul(colliderA.size, 0.5)
    local halfSizeB = MathUtils.vector3Mul(colliderB.size, 0.5)
    
    -- Проверка по осям
    for i = 1, 3 do
        if math.abs(posA[i] - posB[i]) > halfSizeA[i] + halfSizeB[i] then
            return nil
        end
    end
    
    -- Вычисление нормали и проникновения
    local info = Physics.CollisionInfo.new()
    info.hasCollision = true
    info.bodyA = bodyA
    info.bodyB = bodyB
    info.colliderA = colliderA
    info.colliderB = colliderB
    
    -- Находим ось с минимальным проникновением
    local minPenetration = math.huge
    local normalAxis = 1
    
    for i = 1, 3 do
        local penetration = (halfSizeA[i] + halfSizeB[i]) - math.abs(posA[i] - posB[i])
        if penetration < minPenetration then
            minPenetration = penetration
            normalAxis = i
        end
    end
    
    info.penetration = minPenetration
    info.normal = {0, 0, 0}
    if posA[normalAxis] < posB[normalAxis] then
        info.normal[normalAxis] = 1
    else
        info.normal[normalAxis] = -1
    end
    
    -- Точка контакта (упрощенно)
    info.point = {
        (posA[1] + posB[1]) * 0.5,
        (posA[2] + posB[2]) * 0.5,
        (posA[3] + posB[3]) * 0.5
    }
    
    return info
end

-- Sphere vs Sphere коллизия
function CollisionDetection.sphereVsSphere(bodyA, colliderA, bodyB, colliderB)
    local posA = MathUtils.vector3Add(bodyA.position, colliderA.offset)
    local posB = MathUtils.vector3Add(bodyB.position, colliderB.offset)
    
    local delta = MathUtils.vector3Sub(posA, posB)
    local distance = MathUtils.vector3Length(delta)
    local minDistance = colliderA.radius + colliderB.radius
    
    if distance >= minDistance or distance == 0 then
        return nil
    end
    
    local info = Physics.CollisionInfo.new()
    info.hasCollision = true
    info.bodyA = bodyA
    info.bodyB = bodyB
    info.colliderA = colliderA
    info.colliderB = colliderB
    
    info.normal = MathUtils.vector3Normalize(delta)
    info.penetration = minDistance - distance
    info.point = MathUtils.vector3Add(posB, MathUtils.vector3Mul(info.normal, colliderB.radius))
    
    return info
end

-- Box vs Sphere коллизия
function CollisionDetection.boxVsSphere(bodyA, colliderA, bodyB, colliderB)
    local boxPos = MathUtils.vector3Add(bodyA.position, colliderA.offset)
    local spherePos = MathUtils.vector3Add(bodyB.position, colliderB.offset)
    
    local halfSize = MathUtils.vector3Mul(colliderA.size, 0.5)
    
    -- Находим ближайшую точку на AABB к сфере
    local closestPoint = {
        math.max(boxPos[1] - halfSize[1], math.min(spherePos[1], boxPos[1] + halfSize[1])),
        math.max(boxPos[2] - halfSize[2], math.min(spherePos[2], boxPos[2] + halfSize[2])),
        math.max(boxPos[3] - halfSize[3], math.min(spherePos[3], boxPos[3] + halfSize[3]))
    }
    
    -- Расстояние от ближайшей точки до центра сферы
    local delta = MathUtils.vector3Sub(spherePos, closestPoint)
    local distance = MathUtils.vector3Length(delta)
    
    if distance >= colliderB.radius then
        return nil
    end
    
    local info = Physics.CollisionInfo.new()
    info.hasCollision = true
    info.bodyA = bodyA
    info.bodyB = bodyB
    info.colliderA = colliderA
    info.colliderB = colliderB
    
    if distance > 0 then
        info.normal = MathUtils.vector3Normalize(delta)
        info.penetration = colliderB.radius - distance
        info.point = closestPoint
    else
        -- Сфера внутри бокса - находим ближайшую грань
        local distToMin = {
            spherePos[1] - (boxPos[1] - halfSize[1]),
            spherePos[2] - (boxPos[2] - halfSize[2]),
            spherePos[3] - (boxPos[3] - halfSize[3])
        }
        local distToMax = {
            (boxPos[1] + halfSize[1]) - spherePos[1],
            (boxPos[2] + halfSize[2]) - spherePos[2],
            (boxPos[3] + halfSize[3]) - spherePos[3]
        }
        
        local minDist = math.huge
        local normalAxis = 1
        for i = 1, 3 do
            local dist = math.min(distToMin[i], distToMax[i])
            if dist < minDist then
                minDist = dist
                normalAxis = i
            end
        end
        
        info.normal = {0, 0, 0}
        if distToMin[normalAxis] < distToMax[normalAxis] then
            info.normal[normalAxis] = -1
        else
            info.normal[normalAxis] = 1
        end
        
        info.penetration = colliderB.radius + minDist
        info.point = MathUtils.vector3Sub(spherePos, MathUtils.vector3Mul(info.normal, colliderB.radius))
    end
    
    return info
end

-- 2D коллизии (для обратной совместимости)
function CollisionDetection.AABBvsAABB(bodyA, colliderA, bodyB, colliderB)
    -- Упрощенная 2D AABB коллизия (игнорирует Z-координату)
    local aMinX = bodyA.position[1] - colliderA.size[1] * 0.5
    local aMaxX = bodyA.position[1] + colliderA.size[1] * 0.5
    local aMinY = bodyA.position[2] - colliderA.size[2] * 0.5
    local aMaxY = bodyA.position[2] + colliderA.size[2] * 0.5
    
    local bMinX = bodyB.position[1] - colliderB.size[1] * 0.5
    local bMaxX = bodyB.position[1] + colliderB.size[1] * 0.5
    local bMinY = bodyB.position[2] - colliderB.size[2] * 0.5
    local bMaxY = bodyB.position[2] + colliderB.size[2] * 0.5
    
    if aMaxX < bMinX or aMinX > bMaxX or aMaxY < bMinY or aMinY > bMaxY then
        return nil
    end
    
    local info = Physics.CollisionInfo.new()
    info.hasCollision = true
    info.bodyA = bodyA
    info.bodyB = bodyB
    info.colliderA = colliderA
    info.colliderB = colliderB
    
    -- Вычисление нормали и проникновения (2D)
    local overlapX = math.min(aMaxX - bMinX, bMaxX - aMinX)
    local overlapY = math.min(aMaxY - bMinY, bMaxY - aMinY)
    
    if overlapX < overlapY then
        info.penetration = overlapX
        info.normal = {aMaxX - bMinX < bMaxX - aMinX and 1 or -1, 0, 0}
    else
        info.penetration = overlapY
        info.normal = {0, aMaxY - bMinY < bMaxY - aMinY and 1 or -1, 0}
    end
    
    info.point = {
        (bodyA.position[1] + bodyB.position[1]) * 0.5,
        (bodyA.position[2] + bodyB.position[2]) * 0.5,
        0
    }
    
    return info
end

-- Общая функция проверки коллизии
function CollisionDetection.checkCollision(bodyA, colliderA, bodyB, colliderB)
    local typeA, typeB = colliderA.type, colliderB.type
    
    -- Определяем порядок для вызова правильной функции
    if typeA == "box" and typeB == "box" then
        return CollisionDetection.boxVsBox(bodyA, colliderA, bodyB, colliderB)
    elseif typeA == "sphere" and typeB == "sphere" then
        return CollisionDetection.sphereVsSphere(bodyA, colliderA, bodyB, colliderB)
    elseif (typeA == "box" and typeB == "sphere") then
        return CollisionDetection.boxVsSphere(bodyA, colliderA, bodyB, colliderB)
    elseif (typeA == "sphere" and typeB == "box") then
        return CollisionDetection.boxVsSphere(bodyB, colliderB, bodyA, colliderA)
    elseif typeA == "box" and typeB == "box" and colliderA.size[3] == 0.1 and colliderB.size[3] == 0.1 then
        -- 2D коллизия для обратной совместимости
        return CollisionDetection.AABBvsAABB(bodyA, colliderA, bodyB, colliderB)
    end
    
    return nil
end

-- Менеджер физического мира
Physics.World = {}
Physics.World.__index = Physics.World

function Physics.World.new(config)
    local world = setmetatable({}, Physics.World)
    
    world.config = setmetatable(config or {}, {__index = Physics.defaultConfig})
    world.bodies = {}
    world.staticBodies = {}
    world.dynamicBodies = {}
    world.kinematicBodies = {}
    world.collisionPairs = {}
    world.contacts = {}
    world.accumulator = 0
    world.broadphase = Physics.Broadphase.new()
    
    return world
end

function Physics.World:addBody(body)
    table.insert(self.bodies, body)
    
    if body.type == "static" then
        table.insert(self.staticBodies, body)
    elseif body.type == "dynamic" then
        table.insert(self.dynamicBodies, body)
    elseif body.type == "kinematic" then
        table.insert(self.kinematicBodies, body)
    end
    
    body.broadphaseId = self.broadphase:addBody(body)
end

function Physics.World:removeBody(body)
    if body.broadphaseId then
        self.broadphase:removeBody(body.broadphaseId)
    end
    
    local function removeFromTable(tbl, item)
        for i = #tbl, 1, -1 do
            if tbl[i] == item then
                table.remove(tbl, i)
                return true
            end
        end
        return false
    end
    
    removeFromTable(self.bodies, body)
    removeFromTable(self.staticBodies, body)
    removeFromTable(self.dynamicBodies, body)
    removeFromTable(self.kinematicBodies, body)
end

function Physics.World:update(dt)
    self.accumulator = self.accumulator + dt
    
    while self.accumulator >= self.config.fixedTimestep do
        self:fixedUpdate(self.config.fixedTimestep)
        self.accumulator = self.accumulator - self.config.fixedTimestep
    end
end

function Physics.World:fixedUpdate(dt)
    -- Обновление broadphase
    self.broadphase:update()
    
    -- Поиск потенциальных коллизий
    self.collisionPairs = self.broadphase:getPotentialPairs()
    self.contacts = {}
    
    -- Обнаружение коллизий
    self:detectCollisions()
    
    -- Разрешение коллизий
    self:resolveCollisions(dt)
    
    -- Интеграция
    self:integrate(dt)
    
    -- Очистка контактов
    self.contacts = {}
end

function Physics.World:detectCollisions()
    for _, pair in ipairs(self.collisionPairs) do
        local bodyA, bodyB = pair.bodyA, pair.bodyB
        
        if bodyA.enabled and bodyB.enabled then
            -- Проверка масок коллизий
            if (bodyA.collisionLayer & bodyB.collisionMask) ~= 0 and
               (bodyB.collisionLayer & bodyA.collisionMask) ~= 0 then
            
                -- Проверка AABB сначала для оптимизации
                if self:checkAABBOverlap(bodyA, bodyB) then
                    -- Детальная проверка коллайдеров
                    for _, colliderA in ipairs(bodyA.colliders) do
                        for _, colliderB in ipairs(bodyB.colliders) do
                            if colliderA.enabled and colliderB.enabled then
                                local collision = CollisionDetection.checkCollision(
                                    bodyA, colliderA, bodyB, colliderB
                                )
                                if collision then
                                    table.insert(self.contacts, collision)
                                end
                            end
                        end
                    end
                end
            end
        end
    end
end

function Physics.World:checkAABBOverlap(bodyA, bodyB)
    local a = bodyA.aabb
    local b = bodyB.aabb
    
    return not (a.max[1] < b.min[1] or a.min[1] > b.max[1] or
                a.max[2] < b.min[2] or a.min[2] > b.max[2] or
                a.max[3] < b.min[3] or a.min[3] > b.max[3])
end

function Physics.World:resolveCollisions(dt)
    for _, contact in ipairs(self.contacts) do
        local bodyA, bodyB = contact.bodyA, contact.bodyB
        
        if bodyA.isTrigger or bodyB.isTrigger or bodyA.isSensor or bodyB.isSensor then
            -- Триггеры - только события
            if bodyA.onTrigger then bodyA:onTrigger(bodyB, contact) end
            if bodyB.onTrigger then bodyB:onTrigger(bodyA, contact) end
        else
            -- Физическое разрешение коллизии
            self:resolveContact(contact, dt)
            
            -- События коллизии
            if bodyA.onCollision then bodyA:onCollision(bodyB, contact) end
            if bodyB.onCollision then bodyB:onCollision(bodyA, contact) end
        end
    end
end

function Physics.World:resolveContact(contact, dt)
    local bodyA, bodyB = contact.bodyA, contact.bodyB
    
    if bodyA.invMass == 0 and bodyB.invMass == 0 then
        return -- Оба тела статические
    end
    
    -- Относительная скорость
    local relativeVelocity = MathUtils.vector3Sub(bodyB.velocity, bodyA.velocity)
    local velocityAlongNormal = MathUtils.vector3Dot(relativeVelocity, contact.normal)
    
    -- Тела удаляются друг от друга
    if velocityAlongNormal > 0 then
        return
    end
    
    -- Коэффициент восстановления
    local restitution = math.min(bodyA.restitution, bodyB.restitution)
    
    -- Импульс
    local impulseScalar = -(1 + restitution) * velocityAlongNormal
    impulseScalar = impulseScalar / (bodyA.invMass + bodyB.invMass)
    
    local impulse = MathUtils.vector3Mul(contact.normal, impulseScalar)
    
    -- Применение импульсов
    bodyA.velocity[1] = bodyA.velocity[1] - impulse[1] * bodyA.invMass
    bodyA.velocity[2] = bodyA.velocity[2] - impulse[2] * bodyA.invMass
    bodyA.velocity[3] = bodyA.velocity[3] - impulse[3] * bodyA.invMass
    
    bodyB.velocity[1] = bodyB.velocity[1] + impulse[1] * bodyB.invMass
    bodyB.velocity[2] = bodyB.velocity[2] + impulse[2] * bodyB.invMass
    bodyB.velocity[3] = bodyB.velocity[3] + impulse[3] * bodyB.invMass
    
    -- Коррекция позиции (penetration resolution)
    local percent = 0.8 -- обычно 0.2-0.8
    local slop = 0.01   -- допуск проникновения
    local correction = math.max(contact.penetration - slop, 0) / (bodyA.invMass + bodyB.invMass) * percent
    
    local correctionVector = MathUtils.vector3Mul(contact.normal, correction)
    
    if bodyA.invMass > 0 then
        bodyA.position[1] = bodyA.position[1] - correctionVector[1] * bodyA.invMass
        bodyA.position[2] = bodyA.position[2] - correctionVector[2] * bodyA.invMass
        bodyA.position[3] = bodyA.position[3] - correctionVector[3] * bodyA.invMass
    end
    
    if bodyB.invMass > 0 then
        bodyB.position[1] = bodyB.position[1] + correctionVector[1] * bodyB.invMass
        bodyB.position[2] = bodyB.position[2] + correctionVector[2] * bodyB.invMass
        bodyB.position[3] = bodyB.position[3] + correctionVector[3] * bodyB.invMass
    end
end

function Physics.World:integrate(dt)
    -- Применение гравитации к динамическим телам
    for _, body in ipairs(self.dynamicBodies) do
        body:applyForce({
            self.config.gravity[1] * body.mass,
            self.config.gravity[2] * body.mass,
            self.config.gravity[3] * body.mass
        })
        body:update(dt)
    end
    
    -- Обновление kinematic тел
    for _, body in ipairs(self.kinematicBodies) do
        body:update(dt)
    end
end

-- Простая broadphase система (можно заменить на spatial hashing или BVH)
Physics.Broadphase = {}
Physics.Broadphase.__index = Physics.Broadphase

function Physics.Broadphase.new()
    local bp = setmetatable({}, Physics.Broadphase)
    bp.bodies = {}
    bp.nextId = 1
    return bp
end

function Physics.Broadphase:addBody(body)
    local id = self.nextId
    self.bodies[id] = body
    self.nextId = self.nextId + 1
    return id
end

function Physics.Broadphase:removeBody(id)
    self.bodies[id] = nil
end

function Physics.Broadphase:update()
    -- В простой реализации ничего не делаем
    -- В продвинутой - обновляем spatial partitioning
end

function Physics.Broadphase:getPotentialPairs()
    local pairs = {}
    local bodiesArray = {}
    
    for id, body in pairs(self.bodies) do
        if body.enabled then
            table.insert(bodiesArray, body)
        end
    end
    
    -- Простая проверка всех пар (медленно для многих объектов)
    -- В реальной системе использовать spatial partitioning
    for i = 1, #bodiesArray do
        for j = i + 1, #bodiesArray do
            local bodyA, bodyB = bodiesArray[i], bodiesArray[j]
            
            -- Быстрая проверка AABB
            local aabbA, aabbB = bodyA.aabb, bodyB.aabb
            if not (aabbA.max[1] < aabbB.min[1] or aabbA.min[1] > aabbB.max[1] or
                   aabbA.max[2] < aabbB.min[2] or aabbA.min[2] > aabbB.max[2] or
                   aabbA.max[3] < aabbB.min[3] or aabbA.min[3] > aabbB.max[3]) then
                table.insert(pairs, {bodyA = bodyA, bodyB = bodyB})
            end
        end
    end
    
    return pairs
end

-- Raycasting система
function Physics.raycast(world, origin, direction, maxDistance, layerMask)
    local normalizedDir = MathUtils.vector3Normalize(direction)
    local endPoint = MathUtils.vector3Add(origin, MathUtils.vector3Mul(normalizedDir, maxDistance))
    
    local closestHit = nil
    local closestDistance = math.huge
    
    for _, body in ipairs(world.bodies) do
        if body.enabled and (body.collisionLayer & (layerMask or 0xFFFF)) ~= 0 then
            -- Простая проверка пересечения луча с AABB
            local hit, distance = Physics.raycastAABB(origin, endPoint, body.aabb)
            if hit and distance < closestDistance then
                closestHit = body
                closestDistance = distance
            end
        end
    end
    
    return closestHit, closestDistance
end

function Physics.raycastAABB(origin, endPoint, aabb)
    -- Упрощенная реализация raycast против AABB
    local dir = MathUtils.vector3Sub(endPoint, origin)
    local length = MathUtils.vector3Length(dir)
    if length == 0 then return false, 0 end
    
    local invDir = {
        1 / (dir[1] ~= 0 and dir[1] or math.huge),
        1 / (dir[2] ~= 0 and dir[2] or math.huge),
        1 / (dir[3] ~= 0 and dir[3] or math.huge)
    }
    
    local t1 = (aabb.min[1] - origin[1]) * invDir[1]
    local t2 = (aabb.max[1] - origin[1]) * invDir[1]
    local t3 = (aabb.min[2] - origin[2]) * invDir[2]
    local t4 = (aabb.max[2] - origin[2]) * invDir[2]
    local t5 = (aabb.min[3] - origin[3]) * invDir[3]
    local t6 = (aabb.max[3] - origin[3]) * invDir[3]
    
    local tmin = math.max(math.max(math.min(t1, t2), math.min(t3, t4)), math.min(t5, t6))
    local tmax = math.min(math.min(math.max(t1, t2), math.max(t3, t4)), math.max(t5, t6))
    
    if tmax < 0 or tmin > tmax or tmin > length then
        return false, 0
    end
    
    return true, tmin
end

-- Фабричные методы
function Physics.createRigidBody(position, mass, colliders)
    return Physics.Body.new({
        type = "dynamic",
        position = position,
        mass = mass,
        colliders = colliders or {}
    })
end

function Physics.createStaticBody(position, colliders)
    return Physics.Body.new({
        type = "static",
        position = position,
        mass = 0,
        colliders = colliders or {}
    })
end

function Physics.createKinematicBody(position, colliders)
    return Physics.Body.new({
        type = "kinematic",
        position = position,
        colliders = colliders or {}
    })
end

-- Фабрики коллайдеров
function Physics.createBoxCollider(size, offset)
    return Physics.Collider.new("box", {
        size = size,
        offset = offset
    })
end

function Physics.createSphereCollider(radius, offset)
    return Physics.Collider.new("sphere", {
        radius = radius,
        offset = offset
    })
end

function Physics.createCapsuleCollider(radius, height, offset)
    return Physics.Collider.new("capsule", {
        radius = radius,
        height = height,
        offset = offset
    })
end

function Physics.createPlaneCollider(normal, distance, offset)
    return Physics.Collider.new("plane", {
        normal = normal,
        distance = distance,
        offset = offset
    })
end

-- 2D фабрики
function Physics.create2DRigidBody(x, y, mass, colliders)
    return Physics.createRigidBody({x, y, 0}, mass, colliders)
end

function Physics.create2DStaticBody(x, y, colliders)
    return Physics.createStaticBody({x, y, 0}, colliders)
end

function Physics.create2DBoxCollider(width, height, offset)
    return Physics.createBoxCollider({width, height, 0.1}, offset or {0, 0, 0})
end

function Physics.create2DCircleCollider(radius, offset)
    return Physics.createSphereCollider(radius, offset or {0, 0, 0})
end

-- Обратная совместимость
function Physics.Create(player)
    local body = Physics.create2DRigidBody(
        player.x or 0, player.y or 0, 
        player.mass or 1,
        {Physics.create2DBoxCollider(player.w or 1, player.h or 1)}
    )
    
    if player.fallspeed then
        body.userData.fallspeed = player.fallspeed
    end
    if player.maxspeed then
        body.userData.maxspeed = player.maxspeed
    end
    
    body.userData.originalPlayer = player
    Physics.defaultWorld:addBody(body)
    
    return body
end

-- Создание дефолтного мира
Physics.defaultWorld = Physics.World.new()

return Physics